package org.intrasearch.core.io;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class StraightExternalizableOutputStream extends ObjectOutputStream
{
    private final DataOutputStream out;
    private final Map<Class<?>, ExternalizableProvider> classToProvider = new HashMap<Class<?>, ExternalizableProvider>();
    private final Map<ExternalizableProvider, Integer> providerToHandle = new HashMap<ExternalizableProvider, Integer>();

    public StraightExternalizableOutputStream(OutputStream out, SerializationConfig serializationConfig)
            throws IOException
    {
        super();
        this.out = new DataOutputStream(new BufferedOutputStream(out));
        serializationConfig.configure(new Builder());
    }

    @Override
    protected void writeObjectOverride(Object obj) throws IOException
    {
        if (obj == null)
        {
            writeByte(0xFF);
            return;
        }
        ExternalizableProvider externalizableProvider = findProviderFor(obj);
        Integer handle = providerToHandle.get(externalizableProvider);
        if (handle == null)
        {
            throw new IllegalArgumentException("Class " + obj.getClass().getSimpleName() + " is missed in serialization config");
        }
        writeByte(handle);
        externalizableProvider.writeExternal(obj, this);
    }

    private ExternalizableProvider findProviderFor(Object obj)
    {
        Class<? extends Object> aClass = obj.getClass();
        for (Map.Entry<Class<?>, ExternalizableProvider> classExternalizableProviderEntry : classToProvider.entrySet())
        {
            if (classExternalizableProviderEntry.getKey().isAssignableFrom(aClass))
            {
                return classExternalizableProviderEntry.getValue();    
            }
        }
        return null;
    }

    @Override
    public void writeByte(int val) throws IOException
    {
        out.writeByte(val);
    }

    @Override
    public void writeUTF(String str) throws IOException
    {
        out.writeUTF(str);
    }

    public void writeBoolean(boolean v)
            throws IOException
    {
        out.writeBoolean(v);
    }

    public void writeLong(long v)
            throws IOException
    {
        out.writeLong(v);
    }

    public void writeInt(int v)
            throws IOException
    {
        out.writeInt(v);
    }

    public void writeFloat(float v)
            throws IOException
    {
        out.writeFloat(v);
    }

    public void write(int b)
            throws IOException
    {
        out.write(b);
    }

    public void writeShort(int v)
            throws IOException
    {
        out.writeShort(v);
    }

    public void writeChar(int v)
            throws IOException
    {
        out.writeChar(v);
    }

    public void writeDouble(double v)
            throws IOException
    {
        out.writeDouble(v);
    }

    public void writeBytes(String s)
            throws IOException
    {
        out.writeBytes(s);
    }

    public void writeChars(String s)
            throws IOException
    {
        out.writeChars(s);
    }

    public void write(byte[] b, int off, int len)
            throws IOException
    {
        out.write(b, off, len);
    }

    public void write(byte[] b)
            throws IOException
    {
        out.write(b);
    }

    @Override
    public void flush() throws IOException
    {
        out.flush();
    }

    @Override
    public void close() throws IOException
    {
        out.close();
    }

    private class Builder implements SerializationBuilder
    {
        public void addClass(Class<? extends Externalizable> externalizableClass)
        {
            addExternalizableProvider(externalizableClass, new DefaultExternalizableProvider<Externalizable>(externalizableClass));
        }

        public <T> void addExternalizableProvider(Class<? extends T> aClass, ExternalizableProvider<T> externalizableProvider)
        {
            classToProvider.put(aClass, externalizableProvider);
            providerToHandle.put(externalizableProvider, providerToHandle.size());
        }
    }
}
